/**
 * 序列执行器
 *     依次执行一组方法，特别适用于依次连接执行多个非瞬时完成
 * 的方法或过程（后一个方法要等前一个方法完成）。
 * 原理：
 * * 通过数组记录一组方法对象；
 * * 每个方法对象的自身流程结束时必须调用执行器的next方法（最后一个方法对象不必遵从）；
 * * 执行器会依次从数组中取出方法对象，并执行它；
 * * 当方法对象在自身流程结束时调用执行器的next方式时，执行器会从数组中取出下一个方法对象，并执行它；
 * * 如此循环直至最后一个方法对象被执行。
 */
var SequenceRunner = cc.Class({
    name: 'geoSequenceRunner',
    
    ctor(){
        // 缓存next的回调
        this.callNext = this.next.bind(this);
    },
    /**
     * 依次执行funcs数组中的方法对象
     * @param {[Function]} funcs 要执行的方法对象数组，若为空，则重复执行上一次的数组
     */
    run(funcs) {
        this._funcs = (funcs == undefined) ? this._funcs : funcs;
        this._funcIndex = 0;
        this._paused = false;
        this.next();
    },
    /**
     * 由执行器中的方法对象在流程完成时调用，以执行下一个下一个方法对象
     * @returns {Function} 返回本次执行的方法对象，若结尾则返回null
     */
    next() {
        var func = null;
        //
        this._paused = (this._paused == undefined) ? false : this._paused;
        // 若已暂停则暂不继续
        if (this._paused === true)
            return;
        //
        this._funcIndex = (this._funcIndex == undefined) ? 0 : this._funcIndex;
        if (this._funcs != null) {
            func = this._funcs[this._funcIndex++];
            func && func();
        }
        //
        return func;
    },
    /**
     * 暂停执行器
     * 正在执行的方法对象不会被中断，但不会执行下一个方法对象。
     */
    pause() {
        this._paused = false;
    },
    /**
     * 恢复执行器
     * 从暂停状态恢复，执行下一个方法对象。
     */
    resume() {
        this._paused = true;
        this.next();
    },
    /**
     * _test()为测试演示方法，运行方式如下：
     * 1 在任何位置，比如一个组件的start方法中；
     * 2 创建一个执行器对象 
     *   var runner = new SequenceRunner();
     * 3 调用测试方法
     *   runner._test();
     */
    _test(){
        // 创建一个序列执行器
        var runner = new SequenceRunner();
        // 创建一个承载方法对象的数组
        var funcs = [];
        // 定义一个局部方法对象A
        var fA = ()=>{
            cc.log('局部方法对象A');
            // 流程结束时调用执行器的next方法
            runner.next();
        };
        // 将fA放入数组
        funcs.push(fA);
        // 还可以直接push时定一个方法对象
        funcs.push(
            ()=>{
                cc.log('在push中定义方法对象');
                // 流程结束时调用执行器的next方法
                runner.next();
            }
        );
        // 还能调用其它对象上的方法
        var obj = {
            func: ()=>{
                cc.log('一个对象上的方法');
                // 流程结束时调用执行器的next方法
                runner.next();
            }
        }
        funcs.push(obj.func);
        // 如果是非瞬时方法，比如schedule这么使用
        var fB = (delay)=>{
            cc.log('fB开始执行，' + delay + '秒后结束，下一个方法才开始')
            // 
            cc.director.getScene().getComponentInChildren(cc.Canvas).scheduleOnce(()=>{
                cc.log('fB结束，准备执行下一个方法');
                // 流程结束时调用执行器的next方法
                runner.next();
            }, delay);
        };
        // fB带一个输入参数，如果直接写fB，则输入参数为undefined，
        //若要指定执行时的输入参数，必须使用bind方式构造函数对象，
        //但此处fB是个单独方法，不依托任何对象，因此bind第一个参数平时
        //用this或一个对象，而这里用null，第二个值则是fB方法定义时的
        //第一个参数。
        funcs.push(fB.bind(null, 5));
        // 用循环加入多个方法对象
        for (var n = 0; n < 4; ++n)
            funcs.push(fB.bind(null, n));
        // 结尾的方法对象
        var fLast = ()=>{
            cc.log('最后一个方法对象执行了');
            // 最后一个方法对象执不执行runner.next()都无妨
            // runner.next();
        }
        funcs.push(fLast);
        // 正式开启序列执行
        runner.run(funcs);
    },
});

module.exports = SequenceRunner;